package infolayer2.filter;

import java.util.Map;
import java.util.Set;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import infolayer2.graph.Context;
import infolayer2.graph.Edge;
import infolayer2.graph.EdgeSet;
import infolayer2.graph.Graph;
import infolayer2.graph.Node;

/**
 * Extracts the model from imported infolayer data.
 */
public class ModelExtractor {

  class Property {
    int maxCount;
    // Alternative: Set of sets, then find the intersection.
    Set<String> fromId = Sets.newHashSet();
    Set<String> toId = Sets.newHashSet();
    
    public String toString() {
      return "from: "+ fromId + " to: " + toId + "max: " + maxCount;
    }
  }
  
  Context context;
  Graph graph;
  Map<String,Property> properties = Maps.newHashMap();
    
  public ModelExtractor(Graph graph) {
    this.graph = graph;
    this.context = new Context(graph);
    
  }  
    
  
  void addTypes(Edge edge, Property property) {
    if (edge.hasValue()) {
      Object value = edge.getValue();
      if (value instanceof String) {
        property.toId.add("type/text");
      } else if (value instanceof Double) {
        property.toId.add("type/float");
      } else if (value instanceof Boolean) {
        property.toId.add("type/boolean");
      } else {
        System.out.println("Unmapped type: " + value.getClass());
      }
    }
    else {
      Node target = graph.get(edge.getTo());
      if (target == null) {
        System.out.println("Target not found:" + edge.getTo());
      } else {
        for (Edge type: target.get("/type/object/type")) {
          property.toId.add(type.getTo());
        }
      }
    }
  }
  
  private void analyzeProperties() {
    for (Node node: graph) {
      for (String propertyName: node.propertyNames()) {
        if (propertyName.equals("/type/object/type")) {
          continue;
        }
        EdgeSet edges = node.get(propertyName);
        Property property = properties.get(propertyName);
        if (property == null) {
          property = new Property();
          properties.put(propertyName, property);
        }
        for (Edge edge: node.get("/type/object/type")) {
          property.fromId.add(edge.getTo());
        }
        if (edges.size() > property.maxCount) {
          property.maxCount = edges.size();
        }
        for(Edge edge: edges) {
          addTypes(edge, property);
        }
      }
    }
  }
  
  public void generateTypes() {
    Set<String> allTypes = Sets.newHashSet();
    for (Node node: graph) {
      EdgeSet types = node.get("/type/object/type");
      for (Edge type: types) {
        allTypes.add(type.getTo());
      }
    }
    
    for(String name: properties.keySet()) {
      Property property = properties.get(name);
      if (property.toId.size() != 1) {
        continue;
      }
      String toId = property.toId.iterator().next();
      allTypes.add(toId);
      
      for(String fromId: property.fromId) {
        // Create the node for the property
        Node propertyNode = graph.getOrCreate(name);
        propertyNode.addType("/type/type/property");
        propertyNode.addReference("/type/property/schema", fromId);
        propertyNode.addReference("/type/property/expected_type", toId);
        
        Node fromNode = graph.getOrCreate(fromId);
        allTypes.add(fromId);
        fromNode.addReference("/type/type/properties", propertyNode.getId());
      }
    }
    
    for (String typeId: allTypes) {
      Node type = graph.getOrCreate(typeId);
      type.addType("/type/type");
    }
  }
  
  public void run() {
    analyzeProperties();
    generateTypes();
  }


}
